<?php

defined('SYSPATH') or die('Access error!');

/**
 * 验证类
 */
class Validate extends ArrayObject
{

    public static function factory(array $array)
    {
        return new Validate($array);
    }

    /*
     * ============================================================
     *   用于验证的一些函数
     * ===========================================================
     */

    public static function not_empty($value)
    {

        return !in_array($value, array(NULL, FALSE, '', array()), TRUE);
    }

    /**
     * 通过正则表达式验证
     *
     * @param   string  value
     * @param   string  regular expression 
     * @return  boolean
     */
    public static function regex($value, $expression)
    {
        return (bool) preg_match($expression, (string) $value);
    }

    public static function min_length($value, $length)
    {
        return UTF8::strlen($value) >= $length;
    }

    public static function max_length($value, $length)
    {
        return UTF8::strlen($value) <= $length;
    }

    public static function exact_length($value, $length)
    {
        return UTF8::strlen($value) === $length;
    }

    public static function equals($value, $required)
    {
        return ($value === $required);
    }

    public static function email($email)
    {
        $expression = '/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d++)?$/iD';
        return (bool) preg_match($expression, (string) $email);
    }

    public static function url($url)
    {
        if (!preg_match(
                        '~^

			# scheme
			[-a-z0-9+.]++://

			# username:password (optional)
			(?:
				    [-a-z0-9$_.+!*\'(),;?&=%]++   # username
				(?::[-a-z0-9$_.+!*\'(),;?&=%]++)? # password (optional)
				@
			)?

			(?:
				# ip address
				\d{1,3}+(?:\.\d{1,3}+){3}+

				| # or

				# hostname (captured)
				(
					     (?!-)[-a-z0-9]{1,63}+(?<!-)
					(?:\.(?!-)[-a-z0-9]{1,63}+(?<!-)){0,126}+
				)
			)

			# port (optional)
			(?::\d{1,5}+)?

			# path (optional)
			(?:/.*)?

			$~iDx', $url, $matches))
            return FALSE;

        if (!isset($matches[1]))
            return TRUE;

        if (strlen($matches[1]) > 253)
            return FALSE;
        $tld = ltrim(substr($matches[1], (int) strrpos($matches[1], '.')), '.');
        return ctype_alpha($tld[0]);
    }

    public static function ip($ip, $allow_private = TRUE)
    {
        $flags = FILTER_FLAG_NO_RES_RANGE;

        if ($allow_private === FALSE)
        {
            $flags = $flags | FILTER_FLAG_NO_PRIV_RANGE;
        }

        return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
    }

    public static function phone($number, $lengths = NULL)
    {
        if (!is_array($lengths))
        {
            $lengths = array(7, 10, 11);
        }

        $number = preg_replace('/\D+/', '', $number);

        return in_array(strlen($number), $lengths);
    }

    public static function date($str)
    {
        return (strtotime($str) !== FALSE);
    }

    /**
     * 检查一个字符串是否包含字母字符。
     *
     * @param   string   input string
     * @param   boolean  trigger UTF-8 compatibility
     * @return  boolean
     */
    public static function alpha($str, $utf8 = FALSE)
    {
        $str = (string) $str;

        if ($utf8 === TRUE)
        {
            return (bool) preg_match('/^\pL++$/uD', $str);
        }
        else
        {
            return ctype_alpha($str);
        }
    }

    /**
     * 检查一个字符串是否只包含英文字母和数字。
     *
     * @param   string   input string
     * @param   boolean  trigger UTF-8 compatibility
     * @return  boolean
     */
    public static function alpha_numeric($str, $utf8 = FALSE)
    {
        if ($utf8 === TRUE)
        {
            return (bool) preg_match('/^[\pL\pN]++$/uD', $str);
        }
        else
        {
            return ctype_alnum($str);
        }
    }

    /**
     * 检查一个字符串是否只包含字母字符，数字，下划线和破折号。
     *
     * @param   string   input string
     * @param   boolean  trigger UTF-8 compatibility
     * @return  boolean
     */
    public static function alpha_dash($str, $utf8 = FALSE)
    {
        if ($utf8 === TRUE)
        {
            $regex = '/^[-\pL\pN_]++$/uD';
        }
        else
        {
            $regex = '/^[-a-z0-9_]++$/iD';
        }

        return (bool) preg_match($regex, $str);
    }

    /**
     * 检查一个字符串是否包含数字（没有点或破折号）。
     *
     * @param   string   input string
     * @param   boolean  trigger UTF-8 compatibility
     * @return  boolean
     */
    public static function digit($str, $utf8 = FALSE)
    {
        if ($utf8 === TRUE)
        {
            return (bool) preg_match('/^\pN++$/uD', $str);
        }
        else
        {
            return (is_int($str) AND $str >= 0) OR ctype_digit($str);
        }
    }

    /**
     * 检查一个字符串是否是一个有效的数字（允许负和小数）。
     *
     */
    public static function numeric($str)
    {
        list($decimal) = array_values(localeconv());

        return (bool) preg_match('/^-?+(?=.*[0-9])[0-9]*+' . preg_quote($decimal) . '?+[0-9]*+$/D', (string) $str);
    }

    /**
     * 测试一个数是否是在一个范围之内。
     *
     * @param   string   number to check
     * @param   integer  minimum value
     * @param   integer  maximum value
     * @return  boolean
     */
    public static function range($number, $min, $max)
    {
        return ($number >= $min AND $number <= $max);
    }

    /**
     * 检查一个字符串是否是一个正确的十进制格式。
     *
     * @param   string   number to check
     * @param   integer  number of decimal places
     * @param   integer  number of digits
     * @return  boolean
     */
    public static function decimal($str, $places = 2, $digits = NULL)
    {
        if ($digits > 0)
        {
            $digits = '{' . (int) $digits . '}';
        }
        else
        {
            $digits = '+';
        }

        list($decimal) = array_values(localeconv());

        return (bool) preg_match('/^[0-9]' . $digits . preg_quote($decimal) . '[0-9]{' . (int) $places . '}$/D', $str);
    }

    /**
     * 检查一个字符串是否是一个正确的十六进制HTML颜色值
     * 
     * 可以是三位也可以是六位
     *
     * @param   string   input string
     * @return  boolean
     */
    public static function color($str)
    {
        return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str);
    }

    /*
     * ============================================================
     *   下面为核心类的属性和方法
     * ===========================================================
     */

    protected $_filters = array();
    protected $_rules = array();
    protected $_labels = array();
    protected $_empty_rules = array('not_empty', 'matches');
    protected $_errors = array();

    public function __construct(array $array)
    {
        parent::__construct($array, ArrayObject::STD_PROP_LIST);
    }

    public function label($field, $label)
    {
        $this->_labels[$field] = $label;

        return $this;
    }

    public function labels(array $labels)
    {
        $this->_labels = $labels + $this->_labels;

        return $this;
    }

    /**
     *   使用方法
     *
     *     $validation->rule('username', 'not_empty')
     *                ->rule('username', 'min_length', array(4));
     *
     * @param   string  field name
     * @param   string  function or static method name
     * @param   array   extra parameters for the rule
     * @return  $this
     */
    public function rule($field, $rule, array $params = NULL)
    {
        if ($field !== TRUE AND !isset($this->_labels[$field]))
        {
            $this->_labels[$field] = trim(preg_replace('/[^\pL]+/u', ' ', $field));
        }

        if ('matches' === $rule AND !isset($this->_labels[$params[0]]))
        {
            $match_field = $params[0];
            $this->_labels[$match_field] = trim(preg_replace('/[^\pL]+/u', ' ', $match_field));
        }

        $this->_rules[$field][$rule] = (array) $params;

        return $this;
    }

    public function rules($field, array $rules)
    {
        foreach ($rules as $rule => $params)
        {
            $this->rule($field, $rule, $params);
        }

        return $this;
    }

    /**
     * 检查函数
     *
     *     if ($validation->check())
     *     {
     *          
     *     }
     *
     * @param   boolean   allow empty array?
     * @return  boolean
     */
    public function check($allow_empty = FALSE)
    {
        $data = $this->_errors = array();

        $submitted = FALSE;

        $expected = array_keys($this->_labels);

        $rules = $this->_rules;

        foreach ($expected as $field)
        {
            if (isset($this[$field]))
            {
                $submitted = TRUE;
                $data[$field] = $this[$field];
            }
            else
            {
                $data[$field] = NULL;
            }

            if (isset($rules[TRUE]))
            {
                if (!isset($rules[$field]))
                {
                    $rules[$field] = array();
                }
                $rules[$field] = array_merge($rules[$field], $rules[TRUE]);
            }
        }

        $this->exchangeArray($data);

        if ($submitted === FALSE AND !$allow_empty)
        {
            return FALSE;
        }

        unset($rules[TRUE]);



        foreach ($rules as $field => $set)
        {
            $value = $this[$field];

            foreach ($set as $rule => $params)
            {
                if (!in_array($rule, $this->_empty_rules) AND !Validate::not_empty($value))
                {
                    continue;
                }

                array_unshift($params, $value);

                if (method_exists($this, $rule))
                {
                    $method = new ReflectionMethod($this, $rule);

                    if ($method->isStatic())
                    {
                        $passed = $method->invokeArgs(NULL, $params);
                    }
                    else
                    {
                        $passed = call_user_func_array(array($this, $rule), $params);
                    }
                }
                elseif (strpos($rule, '::') === FALSE)
                {
                    $function = new ReflectionFunction($rule);

                    $passed = $function->invokeArgs($params);
                }
                else
                {
                    list($class, $method) = explode('::', $rule, 2);

                    $method = new ReflectionMethod($class, $method);

                    $passed = $method->invokeArgs(NULL, $params);
                }

                if ($passed === FALSE)
                {
                    array_shift($params);

                    $this->error($field, $rule, $params);

                    break;
                }
            }
        }
        return empty($this->_errors);
    }

    public function error($field, $error, array $params = NULL)
    {
        $this->_errors[$field] = array($error, $params);

        return $this;
    }

    /**
     * 调用方法
     *     $errors = $validate->errors('forms/login');
     */
    public function errors($file = NULL, $translate = TRUE)
    {
        if ($file === NULL)
        {
            return $this->_errors;
        }

        $messages = array();

        foreach ($this->_errors as $field => $set)
        {
            list($error, $params) = $set;

            $label = $this->_labels[$field];

            if ($translate)
            {
                if (is_string($translate))
                {
                    $label = __($label, NULL, $translate);
                }
                else
                {
                    $label = __($label);
                }
            }

            $values = array(
                ':field' => $label,
                ':value' => $this[$field],
            );

            if (is_array($values[':value']))
            {
                $values[':value'] = implode(', ', Arr::flatten($values[':value']));
            }

            if ($params)
            {
                foreach ($params as $key => $value)
                {
                    if (is_array($value))
                    {
                        $value = implode(', ', Arr::flatten($value));
                    }

                    if (isset($this->_labels[$value]))
                    {
                        $value = $this->_labels[$value];

                        if ($translate)
                        {
                            if (is_string($translate))
                            {
                                $value = __($value, NULL, $translate);
                            }
                            else
                            {
                                $value = __($value);
                            }
                        }
                    }

                    $values[':param' . ($key + 1)] = $value;
                }
            }

            if ($message = Core::message($file, "{$field}.{$error}") AND is_string($message))
            {
                // Found a message for this field and error
            }
            elseif ($message = Core::message($file, "{$field}.default") AND is_string($message))
            {
                // Found a default message for this field
            }
            elseif ($message = Core::message($file, $error) AND is_string($message))
            {
                // Found a default message for this error
            }
            elseif ($message = Core::message('validate', $error))
            {
                // Found a default message for this error
            }
            else
            {
                // No message exists, display the path expected
                $message = "{$file}.{$field}.{$error}";
            }

            if ($translate)
            {
                if (is_string($translate))
                {
                    // Translate the message using specified language
                    $message = __($message, $values, $translate);
                }
                else
                {
                    // Translate the message using the default language
                    $message = __($message, $values);
                }
            }
            else
            {
                // Do not translate, just replace the values
                $message = strtr($message, $values);
            }

            // Set the message for this field
            $messages[$field] = $message;
        }

        return $messages;
    }

    /**
     * 检查验证
     */
    protected function matches($value, $match)
    {
        return ($value === $this[$match]);
    }

}
